package org.apache.maven.lifecycle.internal;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.maven.execution.MavenSession;
import org.apache.maven.lifecycle.DefaultLifecycles;
import org.apache.maven.lifecycle.DefaultSchedules;
import org.apache.maven.lifecycle.Lifecycle;
import org.apache.maven.lifecycle.LifecycleNotFoundException;
import org.apache.maven.lifecycle.LifecyclePhaseNotFoundException;
import org.apache.maven.lifecycle.MavenExecutionPlan;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginExecution;
import org.apache.maven.plugin.BuildPluginManager;
import org.apache.maven.plugin.InvalidPluginDescriptorException;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoNotFoundException;
import org.apache.maven.plugin.PluginDescriptorParsingException;
import org.apache.maven.plugin.PluginNotFoundException;
import org.apache.maven.plugin.PluginResolutionException;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.Parameter;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.plugin.lifecycle.Execution;
import org.apache.maven.plugin.lifecycle.Phase;
import org.apache.maven.plugin.prefix.NoPluginFoundForPrefixException;
import org.apache.maven.plugin.version.PluginVersionResolutionException;
import org.apache.maven.plugin.version.PluginVersionResolver;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;

/**
 * @since 3.0
 * @author Benjamin Bentmann
 * @author Kristian Rosenvold (Extract class)
 *         <p/>
 *         NOTE: This class is not part of any public api and can be changed or deleted without
 *         prior notice.
 */
@Component(role = LifecycleExecutionPlanCalculator.class)
public class DefaultLifecycleExecutionPlanCalculator implements LifecycleExecutionPlanCalculator {
  @Requirement
  private PluginVersionResolver pluginVersionResolver;

  @Requirement
  private BuildPluginManager pluginManager;

  @Requirement
  private DefaultLifecycles defaultLifeCycles;

  @Requirement
  private DefaultSchedules defaultSchedules;

  @Requirement
  private MojoDescriptorCreator mojoDescriptorCreator;

  @Requirement
  private LifecyclePluginResolver lifecyclePluginResolver;

  @SuppressWarnings({ "UnusedDeclaration" })
  public DefaultLifecycleExecutionPlanCalculator() {
  }

  public DefaultLifecycleExecutionPlanCalculator(BuildPluginManager pluginManager, DefaultLifecycles defaultLifeCycles, MojoDescriptorCreator mojoDescriptorCreator, LifecyclePluginResolver lifecyclePluginResolver, DefaultSchedules defaultSchedules) {
    this.pluginManager = pluginManager;
    this.defaultLifeCycles = defaultLifeCycles;
    this.mojoDescriptorCreator = mojoDescriptorCreator;
    this.lifecyclePluginResolver = lifecyclePluginResolver;
    this.defaultSchedules = defaultSchedules;
  }

  public MavenExecutionPlan calculateExecutionPlan(MavenSession session, MavenProject project, List<Object> tasks,
      boolean setup) throws PluginNotFoundException, PluginResolutionException, LifecyclePhaseNotFoundException,
      PluginDescriptorParsingException, MojoNotFoundException, InvalidPluginDescriptorException,
      NoPluginFoundForPrefixException, LifecycleNotFoundException, PluginVersionResolutionException {
    lifecyclePluginResolver.resolveMissingPluginVersions(project, session);

    final List<MojoExecution> executions = calculateMojoExecutions(session, project, tasks);

    if (setup) {
      setupMojoExecutions(session, project, executions);
    }

    final List<ExecutionPlanItem> planItem = defaultSchedules.createExecutionPlanItem(project, executions);

    return new MavenExecutionPlan(planItem, defaultLifeCycles);
  }

  public MavenExecutionPlan calculateExecutionPlan(MavenSession session, MavenProject project, List<Object> tasks)
      throws PluginNotFoundException, PluginResolutionException, LifecyclePhaseNotFoundException,
      PluginDescriptorParsingException, MojoNotFoundException, InvalidPluginDescriptorException,
      NoPluginFoundForPrefixException, LifecycleNotFoundException, PluginVersionResolutionException {
    return calculateExecutionPlan(session, project, tasks, true);
  }

  private void setupMojoExecutions(MavenSession session, MavenProject project, List<MojoExecution> mojoExecutions)
      throws PluginNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
      MojoNotFoundException, InvalidPluginDescriptorException, NoPluginFoundForPrefixException,
      LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException {
    for (MojoExecution mojoExecution : mojoExecutions) {
      setupMojoExecution(session, project, mojoExecution);
    }
  }

  public void setupMojoExecution(MavenSession session, MavenProject project, MojoExecution mojoExecution)
      throws PluginNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
      MojoNotFoundException, InvalidPluginDescriptorException, NoPluginFoundForPrefixException,
      LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException {
    MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();

    if (mojoDescriptor == null) {
      mojoDescriptor = pluginManager.getMojoDescriptor(mojoExecution.getPlugin(), mojoExecution.getGoal(),
          project.getRemotePluginRepositories(), session.getRepositorySession());

      mojoExecution.setMojoDescriptor(mojoDescriptor);
    }

    populateMojoExecutionConfiguration(project, mojoExecution,
        MojoExecution.Source.CLI.equals(mojoExecution.getSource()));

    finalizeMojoConfiguration(mojoExecution);

    calculateForkedExecutions(mojoExecution, session, project, new HashSet<MojoDescriptor>());
  }

  public List<MojoExecution> calculateMojoExecutions(MavenSession session, MavenProject project, List<Object> tasks)
      throws PluginNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
      MojoNotFoundException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException,
      PluginVersionResolutionException, LifecyclePhaseNotFoundException {
    final List<MojoExecution> mojoExecutions = new ArrayList<MojoExecution>();

    for (Object task : tasks) {
      if (task instanceof GoalTask) {
        String pluginGoal = ((GoalTask) task).pluginGoal;

        MojoDescriptor mojoDescriptor = mojoDescriptorCreator.getMojoDescriptor(pluginGoal, session, project);

        MojoExecution mojoExecution = new MojoExecution(mojoDescriptor, "default-cli", MojoExecution.Source.CLI);

        mojoExecutions.add(mojoExecution);
      } else if (task instanceof LifecycleTask) {
        String lifecyclePhase = ((LifecycleTask) task).getLifecyclePhase();

        Map<String, List<MojoExecution>> phaseToMojoMapping = calculateLifecycleMappings(session, project,
            lifecyclePhase);

        for (List<MojoExecution> mojoExecutionsFromLifecycle : phaseToMojoMapping.values()) {
          mojoExecutions.addAll(mojoExecutionsFromLifecycle);
        }
      } else {
        throw new IllegalStateException("unexpected task " + task);
      }
    }
    return mojoExecutions;
  }

  private Map<String, List<MojoExecution>> calculateLifecycleMappings(MavenSession session, MavenProject project,
      String lifecyclePhase) throws LifecyclePhaseNotFoundException, PluginNotFoundException, PluginResolutionException,
      PluginDescriptorParsingException, MojoNotFoundException, InvalidPluginDescriptorException {
    /*
     * Determine the lifecycle that corresponds to the given phase.
     */

    Lifecycle lifecycle = defaultLifeCycles.get(lifecyclePhase);

    if (lifecycle == null) {
      throw new LifecyclePhaseNotFoundException("Unknown lifecycle phase \"" + lifecyclePhase
          + "\". You must specify a valid lifecycle phase" + " or a goal in the format <plugin-prefix>:<goal> or"
          + " <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: "
          + defaultLifeCycles.getLifecyclePhaseList() + ".", lifecyclePhase);
    }

    /*
     * Initialize mapping from lifecycle phase to bound mojos. The key set of this map denotes the
     * phases the caller is interested in, i.e. all phases up to and including the specified phase.
     */

    Map<String, Map<Integer, List<MojoExecution>>> mappings = new LinkedHashMap<String, Map<Integer, List<MojoExecution>>>();

    for (String phase : lifecycle.getPhases()) {
      Map<Integer, List<MojoExecution>> phaseBindings = new TreeMap<Integer, List<MojoExecution>>();

      mappings.put(phase, phaseBindings);

      if (phase.equals(lifecyclePhase)) {
        break;
      }
    }

    /*
     * Grab plugin executions that are bound to the selected lifecycle phases from project. The
     * effective model of the project already contains the plugin executions induced by the
     * project's packaging type. Remember, all phases of interest and only those are in the lifecyle
     * mapping, if a phase has no value in the map, we are not interested in any of the executions
     * bound to it.
     */

    for (Plugin plugin : project.getBuild().getPlugins()) {
      for (PluginExecution execution : plugin.getExecutions()) {
        // if the phase is specified then I don't have to go fetch the plugin yet and pull it down
        // to examine the phase it is associated to.
        if (execution.getPhase() != null) {
          Map<Integer, List<MojoExecution>> phaseBindings = mappings.get(execution.getPhase());
          if (phaseBindings != null) {
            for (String goal : execution.getGoals()) {
              MojoExecution mojoExecution = new MojoExecution(plugin, goal, execution.getId());
              mojoExecution.setLifecyclePhase(execution.getPhase());
              addMojoExecution(phaseBindings, mojoExecution, execution.getPriority());
            }
          }
        }
        // if not then i need to grab the mojo descriptor and look at the phase that is specified
        else {
          for (String goal : execution.getGoals()) {
            MojoDescriptor mojoDescriptor = pluginManager.getMojoDescriptor(plugin, goal,
                project.getRemotePluginRepositories(), session.getRepositorySession());

            Map<Integer, List<MojoExecution>> phaseBindings = mappings.get(mojoDescriptor.getPhase());
            if (phaseBindings != null) {
              MojoExecution mojoExecution = new MojoExecution(mojoDescriptor, execution.getId());
              mojoExecution.setLifecyclePhase(mojoDescriptor.getPhase());
              addMojoExecution(phaseBindings, mojoExecution, execution.getPriority());
            }
          }
        }
      }
    }

    Map<String, List<MojoExecution>> lifecycleMappings = new LinkedHashMap<String, List<MojoExecution>>();

    for (Map.Entry<String, Map<Integer, List<MojoExecution>>> entry : mappings.entrySet()) {
      List<MojoExecution> mojoExecutions = new ArrayList<MojoExecution>();

      for (List<MojoExecution> executions : entry.getValue().values()) {
        mojoExecutions.addAll(executions);
      }

      lifecycleMappings.put(entry.getKey(), mojoExecutions);
    }

    return lifecycleMappings;
  }

  private void addMojoExecution(Map<Integer, List<MojoExecution>> phaseBindings, MojoExecution mojoExecution,
      int priority) {
    List<MojoExecution> mojoExecutions = phaseBindings.get(priority);

    if (mojoExecutions == null) {
      mojoExecutions = new ArrayList<MojoExecution>();
      phaseBindings.put(priority, mojoExecutions);
    }

    mojoExecutions.add(mojoExecution);
  }

  private void populateMojoExecutionConfiguration(MavenProject project, MojoExecution mojoExecution,
      boolean allowPluginLevelConfig) {
    String g = mojoExecution.getGroupId();

    String a = mojoExecution.getArtifactId();

    Plugin plugin = findPlugin(g, a, project.getBuildPlugins());

    if (plugin == null && project.getPluginManagement() != null) {
      plugin = findPlugin(g, a, project.getPluginManagement().getPlugins());
    }

    if (plugin != null) {
      PluginExecution pluginExecution = findPluginExecution(mojoExecution.getExecutionId(), plugin.getExecutions());

      Xpp3Dom pomConfiguration = null;

      if (pluginExecution != null) {
        pomConfiguration = (Xpp3Dom) pluginExecution.getConfiguration();
      } else if (allowPluginLevelConfig) {
        pomConfiguration = (Xpp3Dom) plugin.getConfiguration();
      }

      Xpp3Dom mojoConfiguration = (pomConfiguration != null) ? new Xpp3Dom(pomConfiguration) : null;

      mojoConfiguration = Xpp3Dom.mergeXpp3Dom(mojoExecution.getConfiguration(), mojoConfiguration);

      mojoExecution.setConfiguration(mojoConfiguration);
    }
  }

  private Plugin findPlugin(String groupId, String artifactId, Collection<Plugin> plugins) {
    for (Plugin plugin : plugins) {
      if (artifactId.equals(plugin.getArtifactId()) && groupId.equals(plugin.getGroupId())) {
        return plugin;
      }
    }

    return null;
  }

  private PluginExecution findPluginExecution(String executionId, Collection<PluginExecution> executions) {
    if (StringUtils.isNotEmpty(executionId)) {
      for (PluginExecution execution : executions) {
        if (executionId.equals(execution.getId())) {
          return execution;
        }
      }
    }

    return null;
  }

  /**
   * Post-processes the effective configuration for the specified mojo execution. This step discards
   * all parameters from the configuration that are not applicable to the mojo and injects the
   * default values for any missing parameters.
   *
   * @param mojoExecution The mojo execution whose configuration should be finalized, must not be
   *        {@code null}.
   */
  private void finalizeMojoConfiguration(MojoExecution mojoExecution) {
    MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();

    Xpp3Dom executionConfiguration = mojoExecution.getConfiguration();
    if (executionConfiguration == null) {
      executionConfiguration = new Xpp3Dom("configuration");
    }

    Xpp3Dom defaultConfiguration = getMojoConfiguration(mojoDescriptor);

    Xpp3Dom finalConfiguration = new Xpp3Dom("configuration");

    if (mojoDescriptor.getParameters() != null) {
      for (Parameter parameter : mojoDescriptor.getParameters()) {
        Xpp3Dom parameterConfiguration = executionConfiguration.getChild(parameter.getName());

        if (parameterConfiguration == null) {
          parameterConfiguration = executionConfiguration.getChild(parameter.getAlias());
        }

        Xpp3Dom parameterDefaults = defaultConfiguration.getChild(parameter.getName());

        parameterConfiguration = Xpp3Dom.mergeXpp3Dom(parameterConfiguration, parameterDefaults, Boolean.TRUE);

        if (parameterConfiguration != null) {
          parameterConfiguration = new Xpp3Dom(parameterConfiguration, parameter.getName());

          if (StringUtils.isEmpty(parameterConfiguration.getAttribute("implementation"))
              && StringUtils.isNotEmpty(parameter.getImplementation())) {
            parameterConfiguration.setAttribute("implementation", parameter.getImplementation());
          }

          finalConfiguration.addChild(parameterConfiguration);
        }
      }
    }

    mojoExecution.setConfiguration(finalConfiguration);
  }

  private Xpp3Dom getMojoConfiguration(MojoDescriptor mojoDescriptor) {
    return MojoDescriptorCreator.convert(mojoDescriptor);
  }

  public void calculateForkedExecutions(MojoExecution mojoExecution, MavenSession session)
      throws MojoNotFoundException, PluginNotFoundException, PluginResolutionException,
      PluginDescriptorParsingException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException,
      LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException {
    calculateForkedExecutions(mojoExecution, session, session.getCurrentProject(), new HashSet<MojoDescriptor>());
  }

  private void calculateForkedExecutions(MojoExecution mojoExecution, MavenSession session, MavenProject project,
      Collection<MojoDescriptor> alreadyForkedExecutions)
      throws MojoNotFoundException, PluginNotFoundException, PluginResolutionException,
      PluginDescriptorParsingException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException,
      LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException {
    MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();

    if (!mojoDescriptor.isForking()) {
      return;
    }

    if (!alreadyForkedExecutions.add(mojoDescriptor)) {
      return;
    }

    List<MavenProject> forkedProjects = LifecycleDependencyResolver.getProjects(project, session,
        mojoDescriptor.isAggregator());

    for (MavenProject forkedProject : forkedProjects) {
      if (forkedProject != project) {
        lifecyclePluginResolver.resolveMissingPluginVersions(forkedProject, session);
      }

      List<MojoExecution> forkedExecutions;

      if (StringUtils.isNotEmpty(mojoDescriptor.getExecutePhase())) {
        forkedExecutions = calculateForkedLifecycle(mojoExecution, session, forkedProject, alreadyForkedExecutions);
      } else {
        forkedExecutions = calculateForkedGoal(mojoExecution, session, forkedProject, alreadyForkedExecutions);
      }

      mojoExecution.setForkedExecutions(BuilderCommon.getKey(forkedProject), forkedExecutions);
    }

    alreadyForkedExecutions.remove(mojoDescriptor);
  }

  private List<MojoExecution> calculateForkedLifecycle(MojoExecution mojoExecution, MavenSession session,
      MavenProject project, Collection<MojoDescriptor> alreadyForkedExecutions)
      throws MojoNotFoundException, PluginNotFoundException, PluginResolutionException,
      PluginDescriptorParsingException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException,
      LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException {
    MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();

    String forkedPhase = mojoDescriptor.getExecutePhase();

    Map<String, List<MojoExecution>> lifecycleMappings = calculateLifecycleMappings(session, project, forkedPhase);

    for (List<MojoExecution> forkedExecutions : lifecycleMappings.values()) {
      for (MojoExecution forkedExecution : forkedExecutions) {
        if (forkedExecution.getMojoDescriptor() == null) {
          MojoDescriptor forkedMojoDescriptor = pluginManager.getMojoDescriptor(forkedExecution.getPlugin(),
              forkedExecution.getGoal(), project.getRemotePluginRepositories(), session.getRepositorySession());

          forkedExecution.setMojoDescriptor(forkedMojoDescriptor);
        }

        populateMojoExecutionConfiguration(project, forkedExecution, false);
      }
    }

    injectLifecycleOverlay(lifecycleMappings, mojoExecution, session, project);

    List<MojoExecution> mojoExecutions = new ArrayList<MojoExecution>();

    for (List<MojoExecution> forkedExecutions : lifecycleMappings.values()) {
      for (MojoExecution forkedExecution : forkedExecutions) {
        if (!alreadyForkedExecutions.contains(forkedExecution.getMojoDescriptor())) {
          finalizeMojoConfiguration(forkedExecution);

          calculateForkedExecutions(forkedExecution, session, project, alreadyForkedExecutions);

          mojoExecutions.add(forkedExecution);
        }
      }
    }

    return mojoExecutions;
  }

  private void injectLifecycleOverlay(Map<String, List<MojoExecution>> lifecycleMappings, MojoExecution mojoExecution,
      MavenSession session, MavenProject project) throws PluginDescriptorParsingException, LifecycleNotFoundException,
      MojoNotFoundException, PluginNotFoundException, PluginResolutionException, NoPluginFoundForPrefixException,
      InvalidPluginDescriptorException, PluginVersionResolutionException {
    MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();

    PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();

    String forkedLifecycle = mojoDescriptor.getExecuteLifecycle();

    if (StringUtils.isEmpty(forkedLifecycle)) {
      return;
    }

    org.apache.maven.plugin.lifecycle.Lifecycle lifecycleOverlay;

    try {
      lifecycleOverlay = pluginDescriptor.getLifecycleMapping(forkedLifecycle);
    } catch (IOException e) {
      throw new PluginDescriptorParsingException(pluginDescriptor.getPlugin(), pluginDescriptor.getSource(), e);
    } catch (XmlPullParserException e) {
      throw new PluginDescriptorParsingException(pluginDescriptor.getPlugin(), pluginDescriptor.getSource(), e);
    }

    if (lifecycleOverlay == null) {
      throw new LifecycleNotFoundException(forkedLifecycle);
    }

    for (Phase phase : lifecycleOverlay.getPhases()) {
      List<MojoExecution> forkedExecutions = lifecycleMappings.get(phase.getId());

      if (forkedExecutions != null) {
        for (Execution execution : phase.getExecutions()) {
          for (String goal : execution.getGoals()) {
            MojoDescriptor forkedMojoDescriptor;

            if (goal.indexOf(':') < 0) {
              forkedMojoDescriptor = pluginDescriptor.getMojo(goal);
              if (forkedMojoDescriptor == null) {
                throw new MojoNotFoundException(goal, pluginDescriptor);
              }
            } else {
              forkedMojoDescriptor = mojoDescriptorCreator.getMojoDescriptor(goal, session, project);
            }

            MojoExecution forkedExecution = new MojoExecution(forkedMojoDescriptor, mojoExecution.getExecutionId());

            Xpp3Dom forkedConfiguration = (Xpp3Dom) execution.getConfiguration();

            forkedExecution.setConfiguration(forkedConfiguration);

            populateMojoExecutionConfiguration(project, forkedExecution, true);

            forkedExecutions.add(forkedExecution);
          }
        }

        Xpp3Dom phaseConfiguration = (Xpp3Dom) phase.getConfiguration();

        if (phaseConfiguration != null) {
          for (MojoExecution forkedExecution : forkedExecutions) {
            Xpp3Dom forkedConfiguration = forkedExecution.getConfiguration();

            forkedConfiguration = Xpp3Dom.mergeXpp3Dom(phaseConfiguration, forkedConfiguration);

            forkedExecution.setConfiguration(forkedConfiguration);
          }
        }
      }
    }
  }
  // org.apache.maven.plugins:maven-remote-resources-plugin:1.0:process
  // TODO: take repo mans into account as one may be aggregating prefixes of many
  // TODO: collect at the root of the repository, read the one at the root, and fetch remote if
  // something is missing
  // or the user forces the issue

  private List<MojoExecution> calculateForkedGoal(MojoExecution mojoExecution, MavenSession session,
      MavenProject project, Collection<MojoDescriptor> alreadyForkedExecutions)
      throws MojoNotFoundException, PluginNotFoundException, PluginResolutionException,
      PluginDescriptorParsingException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException,
      LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException {
    MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();

    PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();

    String forkedGoal = mojoDescriptor.getExecuteGoal();

    MojoDescriptor forkedMojoDescriptor = pluginDescriptor.getMojo(forkedGoal);
    if (forkedMojoDescriptor == null) {
      throw new MojoNotFoundException(forkedGoal, pluginDescriptor);
    }

    if (alreadyForkedExecutions.contains(forkedMojoDescriptor)) {
      return Collections.emptyList();
    }

    MojoExecution forkedExecution = new MojoExecution(forkedMojoDescriptor, forkedGoal);

    populateMojoExecutionConfiguration(project, forkedExecution, true);

    finalizeMojoConfiguration(forkedExecution);

    calculateForkedExecutions(forkedExecution, session, project, alreadyForkedExecutions);

    return Collections.singletonList(forkedExecution);
  }

}
